package org.orange.spring.simulate.vince.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;


//模拟AnnotationConfigApplicationContext
public class VinceApplicationContext {

    private Class configClass;//配置类的Class对象

    //Bean名和它对应的BeanDefinition键值对；
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    //单例池；map存各Bean名所对应的Bean实例
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    private ConcurrentHashMap<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    //存放扫描包路径下的所有BeanPostProcessor类
    private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    //构造方法，参数是配置类的Class对象
    public VinceApplicationContext(Class configClass) {
        //1.赋值配置类成员变量
        this.configClass = configClass;
        // 2.通过反射，判断配置类Class对象有没有注解@ComponentScan；如果有，就解析ComponentScan对象
        // --->BeanDefinition -->beanDefinitionMap
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            //解析ComponentScan对象
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
//            获取ComponentScan对象的value，也就是用户传入的扫描路径。例如@ComponentScan("com.vince.service")，扫描这个包下的Bean
            String path = componentScanAnnotation.value();// com.vince.service
            // 将包名中的点号替换为斜杠
            path = path.replace(".", "/");//com/vince/service
            // 获取容器类的类加载器
            ClassLoader classLoader = VinceApplicationContext.class.getClassLoader();
            // 类加载器获取扫描包的绝对路径
            URL resource = classLoader.getResource(path);//file:/D:/xxx/com/vince/service
            // 把URL绝对路径对象封装成File对象
            File file = new File(resource.getFile());
            // 如果File对象是目录，则遍历判断扫描目录下的文件是不是bean；
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                //遍历“扫描路径”下所有文件中，找到Bean，创建对应BeanDefinition对象，并存到map里；
                // 判断Bean方式：基于反射，判断该字节码文件对应的类有没有@Component注解
                for (File f : files) {
                    String fileName = f.getAbsolutePath();
                    if (fileName.endsWith(".class")) {
                        //将文件名转为类名；D:\xxx\com\vince\service\AppConfig.class ----> AppConfig
                        //这里其实不应该写死成“com”，只是方便起见，其实应该用更复杂的逻辑截取全名为类名
                        String className = fileName.substring(fileName.indexOf("org"), fileName.indexOf(".class"));
                        className = className.replace("\\", ".");
                        try {
                            //根据类的全限定名加载类
                            Class<?> clazz = classLoader.loadClass(className);
                            //如果该类有@Component注解
                            if (clazz.isAnnotationPresent(Component.class)) {
                                System.out.println("容器类构造方法里，扫描@ComponentScan('xx')，发现xx路径下这个类是Bean：" + clazz.getName());
                                //a.如果@Component("类名")有设置类名，Bean名就是设置的这个类名
                                Component component = clazz.getAnnotation(Component.class);
                                String beanName = component.value();

                                //判断这个Bean类是不是BeanPostProcessor接口的实现类，如果是的话就通过反射创建实例，并放进处理器列表里。
                                //注意不能用instanceof，因为instanceof是实例与类的关系比较，isAssignableFrom是类和接口的关系比较
                                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                    BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
                                    beanPostProcessorList.add(instance);
                                    System.out.println("这个Bean是BeanPostProcessor接口的实现类，需要加入list里，以供所有Bean初始化之前和之后增强");
                                }

                                //b.如果@Component没有设置类名，就将首字母小写后的Bean类名设为Bean名
                                if ("".equals(beanName)) {
                                    beanName = Introspector.decapitalize(clazz.getSimpleName());//工具类将字符串校验后首字母小写
                                }

                                // 创建一个BeanDefinition对象，用来描述Bean，里面赋值这个Bean的类型和作用域
                                BeanDefinition beanDefinition = new BeanDefinition();
                                //反射获取@Scope注解指定的作用域，赋值给BeanDefinition对象的scope变量
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scopeAnnotation.value());
                                } else {
                                    beanDefinition.setScope("singleton");
                                }
                                //把这个Bean的Class对象赋值给BeanDefinition对象的type变量
                                beanDefinition.setType(clazz);
                                //把所有Bean名和它对应的BeanDefinition对象映射关系，存到map里统一管理
                                beanDefinitionMap.put(beanName, beanDefinition);
                                System.out.println("Bean名：" + beanName + "，BeanDefinition对象：" + beanDefinition);
                            }
                        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        // 3.实例化bean，如果作用域是单例，则从单例池中取；取不到就创建新的Bean对象，并存入单例池
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equals(beanDefinition.getScope())) {
                if (singletonObjects.get(beanName) == null) {
                    //实例化Bean
                    Object bean = createBean1(beanName, beanDefinition);
//                    singletonObjects.put(beanName, bean);
                }
            }
        }
    }

    //根据Bean名和BeanDefinition对象实例化Bean的方法；私有，只供本类内部调用
    //BeanDefinition对象的成员变量type就是Bean的类型（Class格式）
    private Object createBean1(String beanName, BeanDefinition beanDefinition) {
//        获取Bean的Class格式的类型
        Class clazz = beanDefinition.getType();
        try {
            //1.基于反射创建Bean的实例；
            Object instance = clazz.getConstructor().newInstance();
            earlySingletonObjects.put(beanName, instance);
            System.out.println("实例化：" + beanName + ",对象为：" + instance);
            //2.依赖注入
            //遍历Bean类Class对象的所有属性；
            for (Field f : clazz.getDeclaredFields()) {
                //找出有@Autowired注解的属性，给这些属性进行填充
                if (f.isAnnotationPresent(Autowired.class)) {
                    //破除 private 修饰符访问限制；这样就可以访问注入的这个依赖里面的私有成员
                    f.setAccessible(true);
                    //给该属性填充属性名对应的Bean对象。
                    // 该属性通过getBean能获得Bean，因为上面构造方法里已经扫描了所有的Bean，并创建了BeanDefinition对象加入到“声明map”里了。
                    //void set(Object obj, Object value)
                    //obj 表示要设置属性值的对象；value 表示要为该成员变量设置的新值。
                    Object bean1 = getBean1(f.getName());
                    f.set(instance, bean1);
                    System.out.println("对象为：" + instance + "，注入对象为：" + bean1);
                }
            }

            // 3.Aware回调。如果Bean实例实现了BeanNameAware接口，就调用重写的setter方法，给Bean实例的beanName变量赋值
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // 遍历后置处理器列表，执行后置处理器的before方法，在Bean 的初始化方法（如 @PostConstruct 注解的方法）被调用之前被自动调用
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
//                instance = beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
//                ((InitializingBean) instance).afterPropertiesSet();
            }

            //Bean后置处理器接口的after方法，在 Bean 的初始化方法被调用之后被自动调用
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }
            singletonObjects.put(beanName, instance);
            System.out.println("容器中添加对象：" + beanName + "对象为：" + instance);
            return instance;
        } catch (InstantiationException e) {            //异常处理
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
//        获取Bean的Class格式的类型
        Class clazz = beanDefinition.getType();
        try {
            //1.基于反射创建Bean的实例；
            Object instance = clazz.getConstructor().newInstance();
            //2.依赖注入
            //遍历Bean类Class对象的所有属性；
            for (Field f : clazz.getDeclaredFields()) {
                //找出有@Autowired注解的属性，给这些属性进行填充
                if (f.isAnnotationPresent(Autowired.class)) {
                    //破除 private 修饰符访问限制；这样就可以访问注入的这个依赖里面的私有成员
                    f.setAccessible(true);
                    //给该属性填充属性名对应的Bean对象。
                    // 该属性通过getBean能获得Bean，因为上面构造方法里已经扫描了所有的Bean，并创建了BeanDefinition对象加入到“声明map”里了。
                    //void set(Object obj, Object value)
                    //obj 表示要设置属性值的对象；value 表示要为该成员变量设置的新值。
                    f.set(instance, getBean(f.getName()));
                }
            }

            // 3.Aware回调。如果Bean实例实现了BeanNameAware接口，就调用重写的setter方法，给Bean实例的beanName变量赋值
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // 遍历后置处理器列表，执行后置处理器的before方法，在Bean 的初始化方法（如 @PostConstruct 注解的方法）被调用之前被自动调用
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            //Bean后置处理器接口的after方法，在 Bean 的初始化方法被调用之后被自动调用
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }
            return instance;
        } catch (InstantiationException e) {            //异常处理
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    //容器类的getBean()方法，根据作用域获取Bean
    public Object getBean1(String beanName) {
        //从map里获取BeanDefinition对象
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            //如果BeanDefinition对象为空，说明这个bean没有创建成功，抛异常；
            throw new NullPointerException();
        } else {
            //如果BeanDefinition对象不为空，则判断作用域后获取Bean
            String scope = beanDefinition.getScope();
            if ("singleton".equals(scope)) {
                //如果是单例则从单例池根据Bean名取Bean；单例池是一级缓存，这里简化版暂时先只考虑一级缓存
                Object bean = singletonObjects.get(beanName);
                if (bean == null) {
                    //如果单例池查到的是null，则新创建Bean，再给单例池赋值
                    bean = earlySingletonObjects.get(beanName);
                    if (bean == null) {
                        createBean1(beanName, beanDefinition);
                        bean = earlySingletonObjects.get(beanName);
                    }
                }
                //返回Bean
                return bean;
            } else {
                // 多例，每次直接创建新的
                return createBean(beanName, beanDefinition);
            }
        }
    }

    public Object getBean(String beanName) {
        //从map里获取BeanDefinition对象
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            //如果BeanDefinition对象为空，说明这个bean没有创建成功，抛异常；
            throw new NullPointerException();
        } else {
            //如果BeanDefinition对象不为空，则判断作用域后获取Bean
            String scope = beanDefinition.getScope();
            if ("singleton".equals(scope)) {
                //如果是单例则从单例池根据Bean名取Bean；单例池是一级缓存，这里简化版暂时先只考虑一级缓存
                Object bean = singletonObjects.get(beanName);
                if (bean == null) {
                    //如果单例池查到的是null，则新创建Bean，再给单例池赋值
                    bean = createBean(beanName, beanDefinition);
                    singletonObjects.put(beanName, bean);
                }
                //返回Bean
                return bean;
            } else {
                // 多例，每次直接创建新的
                return createBean(beanName, beanDefinition);
            }
        }
    }
}
